对 SEH 的浅显认识
SEH 是 Windows 操作系统提供的异常处理机制。
使用 __try 关键字注册 SEH
使用 __try 关键字可以创建 SEH:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| int FilterFunc(DWORD dwExceptionCode) { if (dwExceptionCode == EXCEPTION_ACCESS_VIOLATION) { printf("SEH catch\n"); return EXCEPTION_EXECUTE_HANDLER; } return EXCEPTION_CONTINUE_SEARCH; } int main() { __try { int *a=NULL; *a=1; } __except (FilterFunc(GetExceptionCode())){ printf("SEH final\n"); } printf("in main"); return 0; }
|
该段代码在 32 位和 64 位上均有效,不过在汇编层面的具体实现方式不同。
其中__try
关键字和__except
关键字共同完成异常处理,__try
关键字用来捕获异常,__except
关键字根据后面表达式的值来确定当前的异常处理模块是否可以处理该种异常。括号内表达式值有三种:
1 2 3 4
| EXCEPTION_CONTINUE_EXECUTION (–1) EXCEPTION_CONTINUE_SEARCH (0) EXCEPTION_EXECUTE_HANDLER (1)
|
而FilterFunc
函数参数中的GetExceptionCode
函数作用是获取异常代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
|
#define EXCEPTION_ACCESS_VIOLATION STATUS_ACCESS_VIOLATION #define EXCEPTION_DATATYPE_MISALIGNMENT STATUS_DATATYPE_MISALIGNMENT #define EXCEPTION_BREAKPOINT STATUS_BREAKPOINT #define EXCEPTION_SINGLE_STEP STATUS_SINGLE_STEP #define EXCEPTION_ARRAY_BOUNDS_EXCEEDED STATUS_ARRAY_BOUNDS_EXCEEDED #define EXCEPTION_FLT_DENORMAL_OPERAND STATUS_FLOAT_DENORMAL_OPERAND #define EXCEPTION_FLT_DIVIDE_BY_ZERO STATUS_FLOAT_DIVIDE_BY_ZERO #define EXCEPTION_FLT_INEXACT_RESULT STATUS_FLOAT_INEXACT_RESULT #define EXCEPTION_FLT_INVALID_OPERATION STATUS_FLOAT_INVALID_OPERATION #define EXCEPTION_FLT_OVERFLOW STATUS_FLOAT_OVERFLOW #define EXCEPTION_FLT_STACK_CHECK STATUS_FLOAT_STACK_CHECK #define EXCEPTION_FLT_UNDERFLOW STATUS_FLOAT_UNDERFLOW #define EXCEPTION_INT_DIVIDE_BY_ZERO STATUS_INTEGER_DIVIDE_BY_ZERO #define EXCEPTION_INT_OVERFLOW STATUS_INTEGER_OVERFLOW #define EXCEPTION_PRIV_INSTRUCTION STATUS_PRIVILEGED_INSTRUCTION #define EXCEPTION_IN_PAGE_ERROR STATUS_IN_PAGE_ERROR #define EXCEPTION_ILLEGAL_INSTRUCTION STATUS_ILLEGAL_INSTRUCTION #define EXCEPTION_NONCONTINUABLE_EXCEPTION STATUS_NONCONTINUABLE_EXCEPTION #define EXCEPTION_STACK_OVERFLOW STATUS_STACK_OVERFLOW #define EXCEPTION_INVALID_DISPOSITION STATUS_INVALID_DISPOSITION #define EXCEPTION_GUARD_PAGE STATUS_GUARD_PAGE_VIOLATION #define EXCEPTION_INVALID_HANDLE STATUS_INVALID_HANDLE #define EXCEPTION_POSSIBLE_DEADLOCK STATUS_POSSIBLE_DEADLOCK
|
其中常用的如下:
1 2 3 4 5
| EXCEPTION_ACCESS_VIOLATION 非法访问异常 EXCEPTION_BREAKPOINT int 3 断点异常 EXCEPTION_ILLEGAL_INSTRUCTION 无法解析指令异常 EXCEPTION_INT_DIVIDE_BY_ZERO 整数除 0 异常 EXCEPTION_SINGLE_STEP 单步工作模式异常
|
除GetExceptionCode
外,还有一个函数GetExceptionInformation
用于获取异常的详细信息。
另外,还有__try-__finally
语句,finally
没有表达式,即异常直接被finally
中的语句处理(或直接跳过)。
在32位汇编中注册 SEH 异常处理函数
1 2 3
| push SEHfunction push DWORD PTR FS:[0] mov DWORD PTR FS:[0],esp
|
该段代码仅适用于 32 位,64 位中 SEH 由编译器直接注册
对反汇编的 SEH 进行分析
对32位来说很简单,查看上面汇编代码的特征即可:
该段代码由上面的示例代码在 vs2019 中生成
而对64位来说,由于 SEH 不在函数中动态注册,分析起来有一定难度。
对于64位 PE 文件,编译器在其头部嵌入了几乎所有(一小部分特殊函数没有)函数的 SEH 信息。每个函数是否使用了SEH,使用的是什么 SEH(__except还是__finally)等,就在 PE 的Exception Directory
里。所以我们如果要找64位函数所使用的 SEH 的话,需要先取得该函数的相对偏移地址(RVA),然后到该目录下找,示例还是上文的代码,所用工具为CFF Explorer
,异常就在main
函数内触发,main
的偏移为0x11900
。可以看到这里出现的BeginAddress
为异常的__try
块起始位置,EndAddress
为__try
的结束位置,而HandlerAddress
是调用FilterFunc
函数的代码,JumpTarget
是__except
的代码。
(详细信息可参考这位大牛的SEH分析笔记(X64篇) - 云+社区 - 腾讯云)。